import { List } from 'linqts';
import { linq } from '@/lib/linq';
import { Assert } from '@/lib/DebugHelper';
import { AbaqusFileContent } from '@/lib/Abaqus/AbaqusData';
import {
    Node,
    Elem,
    Group,
    GroupName,
    GetGroupName,
    GetSlices,
    FemMesh
} from '@/lib/FemPrimitives';

export class AbaqusExporter {
    private NodeList = new List<Node>();

    private Elem3DList = new List<Elem>();
    private Elem2DList = new List<Elem>();
    private Elem1DList = new List<Elem>();

    private NodeGroups = new List<Group>();
    private Elem3DGroups = new List<Group>();
    private Elem2DGroups = new List<Group>();
    private Elem1DGroups = new List<Group>();

    private NodeGroupNames = new List<GroupName>();
    private Elem3DGroupNames = new List<GroupName>();
    private Elem2DGroupNames = new List<GroupName>();
    private Elem1DGroupNames = new List<GroupName>();

    public DoExport(femMesh: FemMesh): AbaqusFileContent {
        Assert(femMesh);

        this.NodeList = femMesh.NodeList;
        this.Elem3DList = femMesh.Elem3DList;
        this.Elem2DList = femMesh.Elem2DList;
        this.Elem1DList = femMesh.Elem1DList;

        //
        // Convert mesh data
        //
        this.Elem3DGroups = femMesh.Elem3DGroups;
        this.Elem2DGroups = femMesh.Elem2DGroups;
        this.Elem1DGroups = femMesh.Elem1DGroups;
        this.NodeGroups = femMesh.NodeGroups;
        const nodeGroupNames = femMesh.NodeGroupNames;
        const elemGroupNames = femMesh.ElemGroupNames;

        this.NodeGroupNames = this.NodeGroups.Select(g => {
            return {
                GroupId: g.GroupId,
                GroupName: 'nset_' + GetGroupName(nodeGroupNames, g.GroupId).GroupName
            };
        });
        this.Elem3DGroupNames = this.Elem3DGroups.Select(g => {
            return {
                GroupId: g.GroupId,
                GroupName: 'nset_' + GetGroupName(elemGroupNames, g.GroupId).GroupName
            };
        });
        this.Elem2DGroupNames = this.Elem2DGroups.Select(g => {
            return {
                GroupId: g.GroupId,
                GroupName: 'nset_' + GetGroupName(elemGroupNames, g.GroupId).GroupName
            };
        });
        this.Elem1DGroupNames = this.Elem1DGroups.Select(g => {
            return {
                GroupId: g.GroupId,
                GroupName: 'nset_' + GetGroupName(elemGroupNames, g.GroupId).GroupName
            };
        });

        const meshFileContent = this.CreateAbaqusInpFile();

        //
        // Output statistical information
        //
        const femCountInfo = this.GenerateFemCountInfo();

        //
        // return export result
        //
        return {
            MeshFileContent: meshFileContent.ToArray(),
            MeshCountInfo: femCountInfo.ToArray()
        };
    }

    private CreateAbaqusInpFile(): List<string> {
        const t = new Date();
        const timeStr = `${t.getFullYear()}/${t.getMonth() + 1}/${t.getDate()}-${t.getHours()}:${t.getMinutes()}:${t.getSeconds()}`;
        const content = new List<string>([
            '** ',
            '** Abaqus Mesh generated by ',
            '**   MidasGtsExporter (https://wdhust.gitee.io/gts-exporter)',
            `**     at ${timeStr}`,
            '** ',
            '*Heading',
            '*Part, name=Part_gts'
        ]);

        //
        // node definition
        //
        content.Add('*Node, nset=nset_all_node');
        content.AddRange(
            this.NodeList.Select(node => {
                const id = node.Id;
                const x = node.x;
                const y = node.y;
                const z = node.z;
                return `${id},${x.toFixed(6)},${y.toFixed(6)},${z.toFixed(6)}`;
            }).ToArray()
        );

        //
        // element definition
        //
        function GenElemLine(elem: Elem, idIndex: List<number>): string {
            return (
                `${elem.Id},` +
                idIndex
                    .Select(i => `${elem.NodeIds.ElementAt(i - 1)}`)
                    .ToArray()
                    .join(',')
            );
        }

        //
        // 3D elements
        //
        if (this.Elem3DList.Any()) {
            content.Add('*Element, type=C3D8R, elset=eset_3D');
            const elems = this.Elem3DList;
            linq([
                {
                    NodeCount: 8,
                    NodeIdIndex: [1, 2, 3, 4, 5, 6, 7, 8]
                },
                {
                    NodeCount: 6,
                    NodeIdIndex: [1, 2, 3, 3, 4, 5, 6, 6]
                },
                {
                    NodeCount: 5,
                    NodeIdIndex: [1, 2, 3, 4, 5, 5, 5, 5]
                },
                {
                    NodeCount: 4,
                    NodeIdIndex: [1, 2, 3, 3, 4, 4, 4, 4]
                }
            ]).ForEach(ni => {
                const nodeCount = ni!.NodeCount;
                const nodeIdIndex = linq(ni!.NodeIdIndex);
                content.AddRange(
                    elems
                        .Where(e => e!.NodeIds.Count() === nodeCount)
                        .Select(e => GenElemLine(e, nodeIdIndex))
                        .ToArray()
                );
            });
        }

        //
        // 2D elements
        //
        if (this.Elem2DList.Any()) {
            content.Add('*Element, type=S4R, elset=eset_2D');
            const elems = this.Elem2DList;
            linq([
                {
                    NodeCount: 4,
                    NodeIdIndex: [1, 2, 3, 4]
                },
                {
                    NodeCount: 3,
                    NodeIdIndex: [1, 2, 3, 3]
                }
            ]).ForEach(ni => {
                const nodeCount = ni!.NodeCount;
                const nodeIdIndex = linq(ni!.NodeIdIndex);
                content.AddRange(
                    elems
                        .Where(e => e!.NodeIds.Count() === nodeCount)
                        .Select(e => GenElemLine(e, nodeIdIndex))
                        .ToArray()
                );
            });
        }

        //
        // 1D elements
        //
        if (this.Elem1DList.Any()) {
            content.Add('*Element, type=B31, elset=eset_1D');
            const elems = this.Elem1DList;

            const sourceNodeCount = 2;
            const nodeIdIndex = linq([1, 2]);
            content.AddRange(
                elems
                    .Where(e => e!.NodeIds.Count() === sourceNodeCount)
                    .Select(e => GenElemLine(e, nodeIdIndex))
                    .ToArray()
            );
        }

        //
        // node group
        //
        if (this.NodeGroups.Any()) {
            this.NodeGroups.ForEach(group => {
                const groupName = GetGroupName(this.NodeGroupNames, group!.GroupId)
                    .GroupName;
                content.Add(`*Nset, nset=${groupName}`);

                const idCountPerLine = 16;
                const idLines = GetSlices(group!.ItemIds, idCountPerLine).Select(ids =>
                    ids
                        .Select(id => `${id}`)
                        .ToArray()
                        .join(',')
                );
                content.AddRange(idLines.ToArray());
            });
        }

        //
        // 3D element group
        //
        if (this.Elem3DGroups.Any()) {
            this.Elem3DGroups.ForEach(group => {
                const groupName = GetGroupName(this.Elem3DGroupNames, group!.GroupId)
                    .GroupName;
                content.Add(`*Elset, elset=${groupName}`);

                const idCountPerLine = 16;
                const idLines = GetSlices(group!.ItemIds, idCountPerLine).Select(ids =>
                    ids
                        .Select(id => `${id}`)
                        .ToArray()
                        .join(',')
                );
                content.AddRange(idLines.ToArray());
            });
        }

        //
        // 2D element group
        //
        if (this.Elem2DGroups.Any()) {
            this.Elem2DGroups.ForEach(group => {
                const groupName = GetGroupName(this.Elem2DGroupNames, group!.GroupId)
                    .GroupName;
                content.Add(`*Elset, elset=${groupName}`);

                const idCountPerLine = 16;
                const idLines = GetSlices(group!.ItemIds, idCountPerLine).Select(ids =>
                    ids
                        .Select(id => `${id}`)
                        .ToArray()
                        .join(',')
                );
                content.AddRange(idLines.ToArray());
            });
        }

        //
        // 1D element group
        //
        if (this.Elem1DGroups.Any()) {
            this.Elem1DGroups.ForEach(group => {
                const groupName = GetGroupName(this.Elem1DGroupNames, group!.GroupId)
                    .GroupName;
                content.Add(`*Elset, elset=${groupName}`);

                const idCountPerLine = 16;
                const idLines = GetSlices(group!.ItemIds, idCountPerLine).Select(ids =>
                    ids
                        .Select(id => `${id}`)
                        .ToArray()
                        .join(',')
                );
                content.AddRange(idLines.ToArray());
            });
        }

        //
        content.Add('*End Part');

        //
        // Write to file
        //
        return content;
    }

    private GenerateFemCountInfo(): List<string> {
        const nodeCount = this.NodeList.Count();
        const elem3DCount = this.Elem3DList.Count();
        const elem2DCount = this.Elem2DList.Count();
        const elem1DCount = this.Elem1DList.Count();
        const elemCount = elem3DCount + elem2DCount + elem1DCount;

        const femCountInfo = new List<string>([' ****** 输出数据统计 ****** ']);
        femCountInfo.Add(`节点数量：${nodeCount}`);
        if (elemCount > 0) {
            femCountInfo.Add(`单元数量：${elemCount}`);
        }

        if (elem3DCount > 0) {
            femCountInfo.Add(`3D单元数量：${elem3DCount}`);
        }
        if (this.Elem3DGroups.Any()) {
            this.Elem3DGroups.ForEach(g => {
                const count = g!.ItemIds.Count();
                const groupName = GetGroupName(this.Elem3DGroupNames, g!.GroupId)
                    .GroupName;
                femCountInfo.Add(`    ${groupName}: ${count}`);
            });
        }

        if (elem2DCount > 0) {
            femCountInfo.Add(`2D单元数量：${elem2DCount}`);
        }
        if (this.Elem2DGroups.Any()) {
            this.Elem2DGroups.ForEach(g => {
                const count = g!.ItemIds.Count();
                const groupName = GetGroupName(this.Elem2DGroupNames, g!.GroupId)
                    .GroupName;
                femCountInfo.Add(`    ${groupName}: ${count}`);
            });
        }

        if (elem1DCount > 0) {
            femCountInfo.Add(`1D单元数量：${elem1DCount}`);
        }
        if (this.Elem1DGroups.Any()) {
            this.Elem1DGroups.ForEach(g => {
                const count = g!.ItemIds.Count();
                const groupName = GetGroupName(this.Elem1DGroupNames, g!.GroupId)
                    .GroupName;
                femCountInfo.Add(`    ${groupName}: ${count}`);
            });
        }

        return femCountInfo;
    }
}
